package com.szjz.excel.importexport;

import com.szjz.excel.importexport.annotation.ExportMapping;
import com.szjz.excel.importexport.annotation.SheetMapping;
import com.szjz.excel.importexport.exception.ImportExportException;
import com.szjz.excel.importexport.util.StyleUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 导出生成2007格式文件, 暂不支持指定sheet<br>
 * TODO 列宽处理
 */
public class ExcelWriter<T> implements Closeable, Flushable {

    private static final Logger logger = LoggerFactory.getLogger(ExcelWriter.class);

    private OutputStream out;
    public Workbook workbook;
    public Sheet sheet;
    private StyleSet styleSet;
    private CreationHelper creationHelper;

    /**
     * 是否被关闭
     */
    private boolean isClosed = false;
    /**
     * 当前行
     */
    private AtomicInteger currentRow = new AtomicInteger(0);


    /**
     * 创建文件路径
     */
    public static <T> ExcelWriter<T> build(Class<T> tClass, String filePath, String gridTitle) throws IOException {
        if (StringUtils.isBlank(filePath)) {
            throw new ImportExportException("文件路径filePath不能为空");
        }

        File file = new File(filePath);
        //本项目中需要每次都重新创建表格
//        if (!file.exists()) {
        file.getParentFile().mkdirs();
        file.createNewFile();
//        }
        return build(tClass, file, gridTitle);
    }

    /**
     * 校验文件路径
     */
    public static <T> ExcelWriter<T> build(Class<T> tClass, File file, String gridTitle) throws IOException {
        if (file == null) {
            throw new ImportExportException("文件file不能为空");
        }
        if (!file.exists()) {
            throw new ImportExportException("文件file不存在");
        }

        return build(tClass, new FileOutputStream(file), gridTitle);
    }

    /**
     * 创建工作簿 表名为gridTitle
     */
    public static <T> ExcelWriter<T> build(Class<T> clazz, OutputStream out, String gridTitle) throws IOException {
        if (out == null) {
            throw new ImportExportException("输出流out不能为空");
        }
        ExcelWriter<T> excelWriter = new ExcelWriter<>();
        excelWriter.out = out;
        String name = null;


        if (gridTitle == null) {
            // 读取sheetName
            SheetMapping annotation = clazz.getDeclaredAnnotation(SheetMapping.class);
            if (annotation == null) {
                throw new RuntimeException("请添加表名");
            }
            String sheetName = annotation.sheetName();
            int sheetNo = annotation.sheetNo();
            if (StringUtils.isBlank(sheetName) && sheetNo == -1) {
                throw new ImportExportException("请指定sheetName或sheetNo");
            }
            if (StringUtils.isNotBlank(sheetName) && sheetNo >= 0) {
                throw new ImportExportException("请不要同时指定sheetName和sheetNo");
            }
            if (StringUtils.isBlank(sheetName)) {
                sheetName = "sheet" + (sheetNo + 1);
            }

            name = sheetName;
        } else {
            name = gridTitle;
        }
        excelWriter.workbook = new XSSFWorkbook();
        excelWriter.sheet = excelWriter.workbook.createSheet(name);
        excelWriter.styleSet = new StyleSet(excelWriter.workbook);
        excelWriter.creationHelper = excelWriter.workbook.getCreationHelper();

        return excelWriter;
    }


    /**
     * 向工作簿中写入数据
     */
    public ExcelWriter<T> write(Collection<T> rows) {
        if (rows == null) {
            throw new ImportExportException("rows不能为空");
        }
        // 模型字段信息
        LinkedHashMap<String, ExportFieldInfo> fieldInfoMap = null;

        // 写数据
        for (Object object : rows) {
            if (fieldInfoMap == null) {
                fieldInfoMap = FieldInfoUtil.getExportFieldInfo(object.getClass());
                // 获取标题
                List<String> headRow = new ArrayList<>();
                for (Iterator<Map.Entry<String, ExportFieldInfo>> it = fieldInfoMap.entrySet().iterator(); it.hasNext(); ) {
                    Map.Entry<String, ExportFieldInfo> entry = it.next();
                    String headName = entry.getValue().getFieldAnnotation().headName();
                    headRow.add(headName);
                }
                // 写到sheet
                this.writeHeadRow(headRow);
            }

            // 获取内容
            List<FieldValueInfo> dataRow = new ArrayList<>();
            for (Iterator<Map.Entry<String, ExportFieldInfo>> it = fieldInfoMap.entrySet().iterator(); it.hasNext(); ) {
                Map.Entry<String, ExportFieldInfo> entry = it.next();
                Field field = entry.getValue().getField();
                Method getter = entry.getValue().getGetter();
                String format = entry.getValue().getFieldAnnotation().format();
                try {
                    // 调用getter
                    Object value = getter.invoke(object, new Object[]{});
                    dataRow.add(new FieldValueInfo(value, format));
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                    logger.error("模型" + object.getClass().getSimpleName() + "读取" + field.getName() + "值失败", e);
                    throw new ImportExportException("模型" + object.getClass().getSimpleName() + "读取" + field.getName()
                            + "值失败");
                }
            }
            // 写到sheet
            this.writeRow(dataRow);
        }

        // 先写后设置列宽(列不存在时设置列宽是不生效的)
        int i = 0;
        if (fieldInfoMap != null) {
            for (Iterator<Map.Entry<String, ExportFieldInfo>> it = fieldInfoMap.entrySet().iterator(); it.hasNext(); ) {
                Map.Entry<String, ExportFieldInfo> entry = it.next();
                ExportMapping fieldAnnotation = entry.getValue().getFieldAnnotation();
                // 设置列自动宽度
                if (fieldAnnotation.autoSize()) {
                    this.autoSizeColumn(i, true);
                }
                i++;
            }
        }
        return this;
    }


    /**
     * 将生成的Excel表格以流的形式传给前端 前端点击请求连接直接弹窗下载
     *
     * @return
     * @throws IOException
     */
    public void getResponseEntity(HttpServletResponse response) throws IOException {
        Date dt = new Date();//如果不需要格式,可直接用dt,dt就是当前系统时间
        DateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");//设置显示格式
        String currentTime = df.format(dt);
        String fileName = sheet.getSheetName(); //文件名称默认为设置的表格的名称
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(String.format("%s%s", fileName + currentTime, ".xlsx"), "UTF-8"));
        out = response.getOutputStream();
        workbook.write(out);
        out.flush();
        out.close();
    }

    /**
     * 解决文件名称中文乱码问题
     */
    public static String encodeFileName(String fileName, String userAgent) {
        userAgent = userAgent.toLowerCase();
        try {
            if (StringUtils.contains(userAgent, "msie")) {// IE浏览器
                fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
            } else if (StringUtils.contains(userAgent, "mozilla") || StringUtils.contains(userAgent, "firefox")) {// google,火狐浏览器
                fileName = new String(fileName.getBytes(), StandardCharsets.ISO_8859_1.name());
            } else {
                fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());// 其他浏览器
            }
            return fileName;
        } catch (Exception e) {
            return fileName;
        }
    }


    /**
     * 给指定单元格赋值
     *
     * @param x     X坐标，从0计数，既列号
     * @param y     Y坐标，从0计数，既行号
     * @param value 值
     * @return this
     * @since 4.0.2
     */
    public ExcelWriter<T> writeCellValue(int x, int y, Object value) {
        Row row = getOrCreateRow(this.sheet, y);
        Cell cell = getOrCreateCell(row, x);
        FieldValueInfo fieldValueInfo = new FieldValueInfo(value);
        setCellValue(cell, fieldValueInfo, styleSet);
        return this;
    }

    /**
     * 设置某列为自动宽度
     *
     * @param columnIndex    第几列，从0计数
     * @param useMergedCells 是否适用于合并单元格
     * @return this
     * @since 3.3.0
     */
    public ExcelWriter<T> autoSizeColumn(int columnIndex, boolean useMergedCells) {
        this.sheet.autoSizeColumn(columnIndex, useMergedCells);
        return this;
    }

    /**
     * 获取样式集，样式集可以自定义包括：<br>
     *
     * <pre>
     * 1. 头部样式
     * 2. 一般单元格样式
     * 3. 默认整型数字样式
     * 4. 默认浮点数字样式
     * </pre>
     *
     * @return 样式集
     * @since 4.0.0
     */
    public StyleSet getStyleSet() {
        return this.styleSet;
    }

    /**
     * 获取头部样式，获取样式后可自定义样式
     *
     * @return 头部样式
     */
    public CellStyle getHeadCellStyle() {
        return this.styleSet.headCellStyle;
    }

    /**
     * 获取单元格样式，获取样式后可自定义样式
     *
     * @return 单元格样式
     */
    public CellStyle getCellStyle() {
        return this.styleSet.cellStyle;
    }

    /**
     * 继承closeable 关闭工作簿相关内容
     */
    @Override
    public void close() throws IOException {
        this.flush();

        //poi version 3.17 才有close() 方法
        this.workbook.close();

        this.currentRow = null;
        this.styleSet = null;
        this.sheet = null;
        this.workbook = null;
        this.isClosed = true;
    }

    @Override
    public void flush() {
    }

    /**
     * 写出一行标题数据<br>
     * 本方法只是将数据写入Workbook中的Sheet，并不写出到文件<br>
     * 写出的起始行为当前行号，可使用{@link #()}方法调用，根据写出的的行数，当前行号自动+1<br>
     * 样式为默认标题样式，可使用{@link #getHeadCellStyle()}方法调用后自定义默认样式
     *
     * @param rowData 一行的数据(标题)
     * @return this
     */
    private ExcelWriter<T> writeHeadRow(Collection<String> rowData) {
        if (this.isClosed) {
            throw new ImportExportException("ExcelWriter has been closed!");
        }

        // 转换成FieldValueInfo
//        List<FieldValueInfo> fieldValueInfoList = new ArrayList<>();
//        rowData.forEach(value -> {
//            fieldValueInfoList.add(new FieldValueInfo(value));
//        });

        Row row = this.sheet.createRow(this.currentRow.getAndIncrement());

        int i = 0;
        for (String value : rowData) {
            Cell cell = row.createCell(i);
            cell.setCellStyle(styleSet.headCellStyle);
            cell.setCellValue(value);
            i++;
        }

        return this;
    }

    /**
     * 写出一行数据<br>
     * 本方法只是将数据写入Workbook中的Sheet，并不写出到文件<br>
     * 写出的起始行为当前行号，可使用{@link #()}方法调用，根据写出的的行数，当前行号自动+1<br>
     * 样式为默认样式，可使用{@link #getCellStyle()}方法调用后自定义默认样式
     *
     * @param rowData 一行的数据
     * @return this
     */
    private ExcelWriter<T> writeRow(Collection<FieldValueInfo> rowData) {
        if (this.isClosed) {
            throw new ImportExportException("ExcelWriter has been closed!");
        }

        writeRow(this.sheet.createRow(this.currentRow.getAndIncrement()), rowData, this.styleSet);
        return this;
    }

    /**
     * 写一行数据
     *
     * @param row      行
     * @param rowData  一行的数据
     * @param styleSet 单元格样式集，包括日期等样式
     */
    private void writeRow(Row row, Collection<FieldValueInfo> rowData, StyleSet styleSet) {
        int i = 0;
        Cell cell;
        for (FieldValueInfo fieldValueInfo : rowData) {
            cell = row.createCell(i);
            this.setCellValue(cell, fieldValueInfo, styleSet);
            i++;
        }
    }

    /**
     * 设置单元格值
     *
     * @param cell           单元格
     * @param fieldValueInfo 值
     * @param styleSet       单元格样式集，包括日期等样式
     */
    private void setCellValue(Cell cell, FieldValueInfo fieldValueInfo, StyleSet styleSet) {
        // 设置默认样式
        cell.setCellStyle(styleSet.cellStyle);

        Object value = fieldValueInfo.getValue();
        String format = fieldValueInfo.getFormat();

        if (null == value) {
            cell.setCellValue(StringUtils.EMPTY);
        } else if (value instanceof Boolean) {// 布尔值
            cell.setCellValue((Boolean) value);
        } else if (value instanceof RichTextString) {// 富文本
            cell.setCellValue((RichTextString) value);
        } else if (value instanceof Short || value instanceof Integer || value instanceof Long) {// 整型
            cell.setCellStyle(styleSet.cellStyleForInteger);
            cell.setCellValue(((Number) value).longValue());
        } else if (value instanceof Float || value instanceof Double) {// 浮点型
            cell.setCellStyle(styleSet.cellStyleForFloat);
            cell.setCellValue(((Number) value).doubleValue());
        } else if (value instanceof BigDecimal) {// 浮点型
            cell.setCellStyle(styleSet.cellStyleForFloat);
            // poi没有BigDecimal类型, 当作浮点数处理
            cell.setCellValue(((Number) value).doubleValue());
        } else if (value instanceof Date || value instanceof Calendar) {
            if (value instanceof Date) {
                cell.setCellValue((Date) value);
            } else if (value instanceof Calendar) {
                cell.setCellValue((Calendar) value);
            }

            if (StringUtils.isBlank(format)) {
                throw new ImportExportException("日期字段未设置日期格式");
            }
            // 设置日期格式
            CellStyle cellStyle = StyleUtil.cloneCellStyle(cell, styleSet.cellStyle);
            cellStyle.setDataFormat(this.creationHelper.createDataFormat().getFormat(format));
            cell.setCellStyle(cellStyle);
        } else {// 字符串
            cell.setCellValue(value.toString());
        }

    }

    /**
     * 获取已有列或创建新列
     *
     * @param row       Excel表的行
     * @param cellIndex 列号
     * @return {@link Row}
     * @since 4.0.2
     */
    private static Cell getOrCreateCell(Row row, int cellIndex) {
        Cell cell = row.getCell(cellIndex);
        if (null == cell) {
            cell = row.createCell(cellIndex);
        }
        return cell;
    }

    /**
     * 获取已有行或创建新行
     *
     * @param sheet    Excel表
     * @param rowIndex 行号
     * @return {@link Row}
     * @since 4.0.2
     */
    private static Row getOrCreateRow(Sheet sheet, int rowIndex) {
        Row row = sheet.getRow(rowIndex);
        if (null == row) {
            row = sheet.createRow(rowIndex);
        }
        return row;
    }
}

class FieldValueInfo {
    private Object value;
    private String format;

    public FieldValueInfo(Object value) {
        super();
        this.value = value;
    }

    public FieldValueInfo(Object value, String format) {
        super();
        this.value = value;
        this.format = format;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    public String getFormat() {
        return format;
    }

    public void setFormat(String format) {
        this.format = format;
    }
}
